let _Vue = null

export default class VueRouter {
  static install(Vue) {
    // 1. 判断当前插件是否已经被安装
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true
    // 2. 把 Vue 构造函数记录到全局变量
    _Vue = Vue
    // 3. 把创建 Vue 实例时候传入的 router 对象注入到 Vue 实例上
    // 混入
    _Vue.mixin({
      // 混入中的 this 指向的是 Vue 实例
      beforeCreate() {
        // 混入的选项，所有实例中都会有，比如组件中也会执行 beforeCreate
        // 而我们给 Vue 挂载 $router 只需要让他执行一次，所以需要判断组件就不执行，Vue 实例才执行
        // 只有 Vue 的 $options 中才有 router 属性，所以判断 this.$options.router 是否存在就行了
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }


  constructor(options) {
    this.options = options
    // 把 options 中的 routes 解析出来存储到 routeMap
    // 键值对的形式，键就是路由地址，值就是路由组件
    // router-view 这个组件中，会根据当前的路由地址，来 routeMap 里找到对应的路由组件，把它渲染到浏览器中。
    this.routeMap = {}
    // data 是一个响应式的对象，里边有一个属性 current 用于记录当前的路由地址
    // 默认情况下路由地址是 /，也就是根目录
    // Vue 的静态方法 observable 用于创建一个响应式的对象
    this.data = _Vue.observable({
      current: '/'
    })
  }

  init() {
    this.createRouteMap()
    this.initComponents(_Vue)
    this.initEvent()
  }
  // createRouteMap 方法是将构造函数中传入的参数 options 中的 routes 处理成键值对的形式存储到 routeMap 中
  createRouteMap() {
    // 遍历所有的路由规则，把路由规则解析成键值对的形式，存储到 routeMap
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    })
  }


  // initComponents 方法创建两个组件： router-link, router-view

  /**
   * 注意：Vue 的构建版本分为 运行时版 和 完整版
   * 运行时版：不支持 template 模板，需要打包的时候提前编译。使用 render 函数创建虚拟 dom，然后把它渲染到视图。
   * 完整版：包含运行时和编译器，体积比运行时版大 10k 左右，程序运行的时候把模板转换成 render 函数，性能不如运行时版本。
   * vue-cli 创建的项目默认使用的是运行时版本
   * 使用两种方法：
   * 1. 使用完整版
   *  1）在根目录下创建文件 vue.config.js 
   *  2）vue.config.js 文件中配置：
   *      module.exports = {
   *        runtimeCompiler: true
   *      }
   *  3）以下代码
   */
  // initComponents(Vue) {
  //   Vue.component('router-link', {
  //     props: {
  //       to: String
  //     },
  //     template: '<a :href="to"><slot></slot></a>'
  //   })
  // }
  /**
   * 2. 使用运行时版本
   *  1) 删除 vue.config.js 文件
   *  2）如下代码
   */
  initComponents(Vue) {
    const self = this
    Vue.component('router-link', {
      props: {
        to: String
      },
      render(h) {
        // h 函数是 Vue 传过来的，它用来创建虚拟dom，render 函数中调用 h 函数并把它的结果返回
        return h('a', {
          attrs: {
            href: self.options.mode === 'history' ? this.to : `/#${this.to}`
          },
          on: {
            // 注册点击事件
            click: this.clickHandler
          }
        }, [this.$slots.default])
      },
      methods: {
        clickHandler(e) {
          const to = self.options.mode === 'history' ? this.to : `/#${this.to}`
          if (self.routeMap[this.to]) {
            // 改变浏览器路径且不向服务端发送请求
            history.pushState({}, '', to)
            // 加载当前路由组件：将当前路由存储到 data 的current 中
            // this.data 是响应式对象，当 current 值改变，会将视图更新到浏览器
            this.$router.data.current = this.to
          } else {
            history.pushState({}, '', '/#/*')
            this.$router.data.current = '*'
          }

          e.preventDefault()
        }
      }
      // template: '<a :href="to"><slot></slot></a>'
    })

    // router-view 相当于一个占位符
    // 我们要根据当前路由地址，获取到路由组件，渲染到 router-view 的位置
    Vue.component('router-view', {
      render(h) {
        const component = self.routeMap[self.data.current]
        // h 函数将组件转换为虚拟 dom 并返回
        return h(component)
      }
    })
  }

  // 点击浏览器的前进后退按钮时，渲染当前地址栏中的地址对应的组件
  initEvent() {
    if (this.options.mode === 'history') {
      window.addEventListener('popstate', () => {
        this.data.current = this.routeMap[window.location.pathname] ? window.location.pathname : '*'
      })
    } else {
      window.addEventListener('hashchange', () => {
        const hash = window.location.hash
        this.data.current = hash ? (this.routeMap[hash.substr(1)] ? hash.substr(1) : '*') : '/'
      })
    }

  }
}
